/* Copyright (c) 2008, 2009, 2010, 2011 Damian Kmiecik
   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:

   * Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in
     the documentation and/or other materials provided with the
     distribution.
   * Neither the name of the copyright holders nor the names of
     contributors may be used to endorse or promote products derived
     from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGE. */

#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include "config.h"
#include "hardware.h"
#include "stdout.h"
#include "timer.h"
#include "main.h"
#include "spi.h"
#include "enc28j60reg.h"
#include "enc28j60.h"

#ifndef ENC_DEBUG
	#define ENC_DEBUG(...)
#endif

#define ENC28J60_ENABLE		NETCARD_ENA
#define ENC28J60_DISABLE	NETCARD_DIS

uint8_t enc28j60Bank = ENC28J60_BANK_0;
uint16_t enc28j60NextPacketPtr = ENC28J60_RXST;

/**
 * System Reset Command
 */
static inline void enc28j60Reset(void) {
	ENC28J60_ENABLE;
	spiWrite(ENC28J60_OPC_SRC);
	ENC28J60_DISABLE;
}

/**
 * Read Control Register Command
 * @param uint8_t address
 * @return uint8_t
 */
uint8_t enc28j60ReadCR(const uint8_t address) {
	uint8_t data;
	// Select bank
	enc28j60BankSel(address);
	ENC28J60_ENABLE;
	// Select action and address
	spiWrite(ENC28J60_OPC_RCR | (address & ENC28J60_REG_MASK));
	// Check for dummy byte flag
	if (address & ENC28J60_REG_DUMMY_MASK)
		spiRead();
	// Read data
	data = spiRead();
	ENC28J60_DISABLE;
	return data;
}

/**
 * Write Control Register Command
 * @param uint8_t address
 * @param uint8_t data
 */
void enc28j60WriteCR(const uint8_t address, const uint8_t data) {
	// Select bank
	enc28j60BankSel(address);
	ENC28J60_ENABLE;
	// Select action and address
	spiWrite(ENC28J60_OPC_WCR | (address & ENC28J60_REG_MASK));
	// Write data
	spiWrite(data);
	ENC28J60_DISABLE;
}

/**
 * Read PHY Register Command
 * @param uint8_t address
 * @return uint16_t
 */
uint16_t enc28j60ReadPHY(const uint8_t address) {
	// Select address of PHY register
	enc28j60WriteCR(ENC28J60_REG_MIREGADR, address);
	// Set MICMD.MIIRD bit
	enc28j60WriteCR(ENC28J60_REG_MICMD, ENC28J60_MICMD_MIIRD);
	// Loop until BUSY bit is set
	while (enc28j60ReadCR(ENC28J60_REG_MISTAT) & ENC28J60_MISTAT_BUSY)
		;
	// Clear MICMD.MIIRD bit
	enc28j60WriteCR(ENC28J60_REG_MICMD, 0x00);
	// Read data from MIRDL and MIRDH
	return enc28j60ReadCR(ENC28J60_REG_MIRDL) | (enc28j60ReadCR(
			ENC28J60_REG_MIRDH) << 8);
}

/**
 * Write PHY Register Command
 * @param uint8_t address
 * @param uint8_t data
 */
void enc28j60WritePHY(const uint8_t address, const uint16_t data) {
	// Select address of PHY register
	enc28j60WriteCR(ENC28J60_REG_MIREGADR, address);
	// Write low part of data to MIWRL
	enc28j60WriteCR(ENC28J60_REG_MIWRL, data);
	// Write high part of data to MIWRH
	enc28j60WriteCR(ENC28J60_REG_MIWRH, (data >> 8));
	// Loop until BUSY bit is set
	while (enc28j60ReadCR(ENC28J60_REG_MISTAT) & ENC28J60_MISTAT_BUSY)
		;
}

/**
 * Set bits in register
 * @param uint8_t address
 * @param uint8_t mask
 */
void enc28j60SetBF(const uint8_t address, const uint8_t mask) {
	// Select bank
	enc28j60BankSel(address);
	ENC28J60_ENABLE;
	// Select action and address
	spiWrite(ENC28J60_OPC_BFS | (address & ENC28J60_REG_MASK));
	// Write mask
	spiWrite(mask);
	ENC28J60_DISABLE;
}

/**
 * Clear bits in register
 * @param uint8_t address
 * @param uint8_t mask
 */
void enc28j60ClearBF(const uint8_t address, const uint8_t mask) {
	// Select bank
	enc28j60BankSel(address);
	ENC28J60_ENABLE;
	// Select action and address
	spiWrite(ENC28J60_OPC_BFC | (address & ENC28J60_REG_MASK));
	// Write mask
	spiWrite(mask);
	ENC28J60_DISABLE;
}

/**
 * Bank Select
 * Check if destination address is in current bank
 * @param uint8_t address
 */
void enc28j60BankSel(const uint8_t address) {
	// If requested bank isn't active
	if (((address & ENC28J60_BANK_MASK) != enc28j60Bank) && ((address
			& ENC28J60_REG_MASK) < 0x1A)) {
		enc28j60Bank = address & ENC28J60_BANK_MASK;
		ENC28J60_ENABLE;
		// Set 0 to BSEL0 and BSEL1
		spiWrite(ENC28J60_OPC_BFC | (ENC28J60_REG_ECON1 & ENC28J60_REG_MASK));
		spiWrite(ENC28J60_ECON1_BSEL0 | ENC28J60_ECON1_BSEL1);
		ENC28J60_DISABLE;
		ENC28J60_ENABLE;
		// Put BSEL1 and BSEL0
		spiWrite(ENC28J60_OPC_BFS | (ENC28J60_REG_ECON1 & ENC28J60_REG_MASK));
		spiWrite((address & ENC28J60_BANK_MASK) >> 5);
		ENC28J60_DISABLE;
	}
}

/**
 * ENC28J60 initialize
 * Must be run before any usage of ENC28J60 functions
 */
void enc28j60Init(void)
{
	ENC28J60_DISABLE;
	// Reset the device
	enc28j60Reset();
	_delay_ms(10);
#ifdef USE_25MHz
	enc28j60SetCLK(ENC28J60_ECOCON_25_MHz);
#else
	enc28j60SetCLK(ENC28J60_ECOCON_12_5_MHz);
#endif
	// Configure LED's
	enc28j60WritePHY(ENC28J60_REG_PHLCON, 0b0010001110100);
	// Write receive buffer start address
	// WARNING according B5 silicon errata, ERXST should be 0x0000
	// BUG Sometimes, when ERXST or ERXND are written to, the exact value, 0000h,
	//     is stored in the internal receive Write Pointer
	//     instead of the ERXST address. (errata chapter 3)
	enc28j60WriteCR(ENC28J60_REG_ERXSTL, (ENC28J60_RXST & 0xFF));
	enc28j60WriteCR(ENC28J60_REG_ERXSTH, ((ENC28J60_RXST >> 8) & 0xFF));
	// Set receive pointer address (ADDITIONALLY)
	enc28j60WriteCR(ENC28J60_REG_ERXRDPTL, (ENC28J60_RXST & 0xFF));
	enc28j60WriteCR(ENC28J60_REG_ERXRDPTH, ((ENC28J60_RXST >> 8) & 0xFF));
	// Write receive buffer end address
	enc28j60WriteCR(ENC28J60_REG_ERXNDL, (ENC28J60_RXND & 0xFF));
	enc28j60WriteCR(ENC28J60_REG_ERXNDH, ((ENC28J60_RXND >> 8) & 0xFF));
	// NOTE No explicit action is required to initialize the transmission buffer
	// Write transmission buffer start address
	enc28j60WriteCR(ENC28J60_REG_ETXSTL, (ENC28J60_TXST & 0xFF));
	enc28j60WriteCR(ENC28J60_REG_ETXSTH, ((ENC28J60_TXST >> 8) & 0xFF));
	// Write transmission buffer end address
	enc28j60WriteCR(ENC28J60_REG_ETXNDL, (ENC28J60_TXND & 0xFF));
	enc28j60WriteCR(ENC28J60_REG_ETXNDH, ((ENC28J60_TXND >> 8) & 0xFF));
	// MAC Initialization
	enc28j60WriteCR(ENC28J60_REG_MACON2, 0x00);
	enc28j60WriteCR(ENC28J60_REG_MACON1, ENC28J60_MACON1_MARXEN
			|ENC28J60_MACON1_TXPAUS
			|ENC28J60_MACON1_RXPAUS);
	enc28j60WriteCR(ENC28J60_REG_MACON3, ENC28J60_MACON3_PADCFG0
			|ENC28J60_MACON3_TXCRCEN
			|ENC28J60_MACON3_FRMLNEN);
	// Enable retransmitting after any collision
	//enc28j60WriteCR(ENC28J60_REG_MACON4, ENC28J60_MACON4_NOBKOFF);
	// Set max frame length
	enc28j60WriteCR(ENC28J60_REG_MAMXFLL, (ENC28J60_MAX_LEN & 0xFF));
	enc28j60WriteCR(ENC28J60_REG_MAMXFLH, ((ENC28J60_MAX_LEN >> 8) & 0xFF));
	// Back-to-back
	// NOTE see datasheet chapter 6.5
	// NOTE Do not fuck with this
	enc28j60WriteCR(ENC28J60_REG_MABBIPG, 0x12);
	enc28j60WriteCR(ENC28J60_REG_MAIPGL, 0x12);
	enc28j60WriteCR(ENC28J60_REG_MAIPGH, 0x0C);
	// Write MAC address
	enc28j60WriteCR(ENC28J60_REG_MAADR0, settings.mac[0]);
	enc28j60WriteCR(ENC28J60_REG_MAADR1, settings.mac[1]);
	enc28j60WriteCR(ENC28J60_REG_MAADR2, settings.mac[2]);
	enc28j60WriteCR(ENC28J60_REG_MAADR3, settings.mac[3]);
	enc28j60WriteCR(ENC28J60_REG_MAADR4, settings.mac[4]);
	enc28j60WriteCR(ENC28J60_REG_MAADR5, settings.mac[5]);
	// No loopback of transmitted frames
	enc28j60WritePHY(ENC28J60_REG_PHCON2, ENC28J60_PHCON2_HDLDIS);
	// Enable interrutps
	enc28j60WriteCR(ENC28J60_REG_EIE, ENC28J60_EIE_INTIE
			|ENC28J60_EIE_PKTIE);
	// Enable packet reception
	enc28j60SetBF(ENC28J60_REG_ECON1, ENC28J60_ECON1_RXEN);
}

/**
 * Read Buffer Memmory Command
 * @param uint16_t len
 * @param uint8_t* data
 */
void enc28j60ReadBM(uint16_t len, uint8_t *data) {
	ENC28J60_ENABLE;
	//Select action
	spiWrite(ENC28J60_OPC_RBM);
	while (len) {
		len--;
		// Read data
		*data = spiRead();
		data++;
	}
	// End of data
	*data = '\0';
	ENC28J60_DISABLE;
}

/**
 * Write Buffer Memmory Command
 * @param uint16_t len
 * @param uint8_t* data
 */
void enc28j60WriteBM(uint16_t len, const uint8_t* data) {
	ENC28J60_ENABLE;
	// Select action
	spiWrite(ENC28J60_OPC_WBM);
	while (len) {
		len--;
		// Write data
		spiWrite(*data);
		data++;
	}
	ENC28J60_DISABLE;
}

/**
 * Send Packet
 * @param uint16_t len
 * @param uint8_t* packet
 */
void enc28j60SendPacket(const uint16_t len, const uint8_t* packet) {
	// Wait until transmitmlogic is idle
	uint8_t at = 0;
	while ((enc28j60ReadCR(ENC28J60_REG_ECON1) & ENC28J60_ECON1_TXRTS) && (at
			< 10)) {
		at++;
		_delay_us(50);
	}
	// Reset
	if (enc28j60ReadCR(ENC28J60_REG_ECON1) & ENC28J60_ECON1_TXRTS) {
		enc28j60SetBF(ENC28J60_REG_ECON1, ENC28J60_ECON1_TXRST);
		enc28j60ClearBF(ENC28J60_REG_ECON1, ENC28J60_ECON1_TXRST);
	}
	// Set transmission start pointer to begin of the packet
	enc28j60WriteCR(ENC28J60_REG_EWRPTL, (ENC28J60_TXST & 0xFF));
	enc28j60WriteCR(ENC28J60_REG_EWRPTH, ((ENC28J60_TXST >> 8) & 0xFF));
	// Set transmission start pointer to end of the packet
	enc28j60WriteCR(ENC28J60_REG_ETXNDL, ((ENC28J60_TXST + len) & 0xFF));
	enc28j60WriteCR(ENC28J60_REG_ETXNDH, (((ENC28J60_TXST + len) >> 8) & 0xFF));
	// Write packet control byte (use default settings)
	// NOTE see datasheet chapter 7.1
	ENC28J60_ENABLE;
	spiWrite(ENC28J60_OPC_WBM);
	spiWrite(0x00);
	ENC28J60_DISABLE;
	// Copy the packet into the transmit buffer
	enc28j60WriteBM(len, packet);
	// WARNING according B5 silicon errata, Reset the transmit logic problem
	// BUG In Half-Duplex mode, a hardware transmission
	//     abort caused by excessive collisions, a late collision
	//     or excessive deferrals, may stall the internal trans-
	//     mit logic. The next packet transmit initiated by the
	//     host controller may never succeed (ECON1.TXRTS
	//     will remain set indefinitely) (errata chapter 10)
	//enc28j60SetBF(ENC28J60_REG_ECON1, ENC28J60_ECON1_TXRST);
	//enc28j60ClearBF(ENC28J60_REG_ECON1, ENC28J60_ECON1_TXRST);
	enc28j60ClearBF(ENC28J60_REG_EIR, ENC28J60_EIR_TXERIF);
	// Send the contents of the transmit buffer onto the network
	enc28j60SetBF(ENC28J60_REG_ECON1, ENC28J60_ECON1_TXRTS);
	// WARNING according B5 silicon errata, Reset the transmit logic problem
	// BUG In Half-Duplex mode, a hardware transmission
	//     abort caused by excessive collisions, a late collision
	//     or excessive deferrals, may stall the internal trans-
	//     mit logic. The next packet transmit initiated by the
	//     host controller may never succeed (ECON1.TXRTS
	//     will remain set indefinitely) (errata chapter 10)
	//if ((enc28j60ReadCR(ENC28J60_REG_EIR) & ENC28J60_EIR_TXERIF))
	//	enc28j60ClearBF(ENC28J60_REG_ECON1, ENC28J60_ECON1_TXRTS);
}

/**
 * Receive Packet
 * @param uint16_t maxlen
 * @param uint8_t* packet
 * @return uint16_t
 */
uint16_t enc28j60ReceivePacket(const uint16_t maxlen, uint8_t* packet) {
	uint16_t rxstat;
	uint16_t len;
	// WARNING according B5 silicon errata, Checking EPKTCNT == 0 instead of flag PKTIF
	// BUG Checking EIR.PKTIF doesn't works (errata chapter 4)
	if (enc28j60ReadCR(ENC28J60_REG_EPKTCNT) == 0)
		return 0;
	// Set the read pointer to the start of the received packet
	enc28j60WriteCR(ENC28J60_REG_ERDPTL, (enc28j60NextPacketPtr & 0xFF));
	enc28j60WriteCR(ENC28J60_REG_ERDPTH, ((enc28j60NextPacketPtr >> 8) & 0xFF));
	// Read the next packet pointer
	// NOTE more info datasheet chapter 7.2.2
	ENC28J60_ENABLE;
	spiWrite(ENC28J60_OPC_RBM);
	enc28j60NextPacketPtr = spiRead();
	ENC28J60_DISABLE;
	ENC28J60_ENABLE;
	spiWrite(ENC28J60_OPC_RBM);
	enc28j60NextPacketPtr |= (spiRead() << 8);
	ENC28J60_DISABLE;
	// Read the packet length
	// NOTE more info datasheet chapter 7.2.2 table 7-3
	ENC28J60_ENABLE;
	spiWrite(ENC28J60_OPC_RBM);
	len = spiRead();
	ENC28J60_DISABLE;
	ENC28J60_ENABLE;
	spiWrite(ENC28J60_OPC_RBM);
	len |= (spiRead() << 8);
	ENC28J60_DISABLE;
	// Read the receive status
	// NOTE more info datasheet chapter 7.2.2 table 7-3
	ENC28J60_ENABLE;
	spiWrite(ENC28J60_OPC_RBM);
	rxstat = spiRead();
	ENC28J60_DISABLE;
	ENC28J60_ENABLE;
	spiWrite(ENC28J60_OPC_RBM);
	rxstat |= (spiRead() << 8);
	ENC28J60_DISABLE;
	// ENC28J60 ERROR!!!
	if ((!(rxstat & (1<<7))) || (rxstat & 0x8000) || (len > maxlen))
	{
		ENC_DEBUG("ENC28J60 ERROR\n", NULL);
		// Reset!
		enc28j60Init();
	}
	// Remove the CRC count
	// NOTE more info datasheet chapter 7.2.2
	len -= 4;
	// Limit packet length
	if (len > maxlen - 1)
		len = maxlen - 1;
	// Check is packzet Recived OK
	if ((rxstat & ENC28J60_RSV_RECIVED_OK))
		// Read packet from buffer memmory
		enc28j60ReadBM(len, packet);
	else
		// Invalid packet
		len = 0;
	// Freeing memmory
	enc28j60WriteCR(ENC28J60_REG_ERXRDPTL, (enc28j60NextPacketPtr & 0xFF));
	enc28j60WriteCR(ENC28J60_REG_ERXRDPTH,
			((enc28j60NextPacketPtr >> 8) & 0xFF));
	// Decrement the packet counter
	enc28j60SetBF(ENC28J60_REG_ECON2, ENC28J60_ECON2_PKTDEC);
	return len;
}

/**
 * Get link status
 */
uint8_t enc28j60LinkStatus(void)
{
	return (enc28j60ReadPHY(ENC28J60_REG_PHSTAT1) & ENC28J60_PHSTAT1_LLSTAT);
}
